//! $Id$
//!
//! @file    ResolverCache.h
//! @author  Johan Andersson <skagget77@gmail.com>
//! @date    Thu Oct 02 07:35 2009
//! @ingroup IoCppDetail
//!

#if !defined(IOCPP_RESOLVERCACHE_H)
#define IOCPP_RESOLVERCACHE_H

#include "IoCpp/Exception.h"
#include "IoCpp/Detail/TypeInfo.h"

#include <boost/any.hpp>
#include <boost/function.hpp>
#include <string>
#include <map>

// Class needs to have dll-interface to be used by clients.
#pragma warning(disable: 4251)

namespace IoCpp
{
namespace Detail
{
   //!
   //! @author  Johan Andersson <skagget77@gmail.com>
   //! @date    Thu Oct 02 07:35 2009
   //! @ingroup IoCppDetail
   //!
   //! The ResolverCache class.
   //!
   class IOCPP_API ResolverCache
   {
   public :
      //!
      //! Creates a new instance of the ResolverCache class.
      //!
      ResolverCache();

      //!
      //! Adds a named provider to the cache.
      //!
      //! @param name The provider's name.
      //!
      //! @param provider The provider.
      //!
      template<class T1>
      void AddProvider( const std::string& name, boost::function<T1 ()> provider );

      //!
      //! Resolves the specified type and returns an instance to it.
      //!
      //! @return An instance of the resolved type.
      //!
      template<class T1>
      T1 Resolve() const;

      //!
      //! Resolves the specified type and returns an instance to it. When the 
      //! type matches more than one instance the name parameter is used to 
      //! resolve which one to inject.
      //!
      //! @param  name A user defined name to resolve potential ambiguities.
      //!
      //! @return An instance to the resolved type.
      //!
      template<class T1>
      T1 Resolve( const std::string& name ) const;

   private :
      // Prevent copying.
      ResolverCache( const ResolverCache& );
      ResolverCache& operator=( const ResolverCache& );

      // Definition of a collection of named providers.
      typedef std::map<std::string,boost::any> ProviderCollection;

      // Definition of a collection of managed types.
      typedef std::map<TypeInfo,ProviderCollection> TypeCollection;

      // Collection of managed types.
      TypeCollection types;
   };


   template<class T1>
   void ResolverCache::AddProvider( const std::string& name, boost::function<T1 ()> provider ) {
      types[typeid(T1)].insert(std::make_pair(name, provider));
   }


   template<class T1>
   T1 ResolverCache::Resolve() const 
   {
      // Find type to inject.
      TypeCollection::const_iterator it1 = types.find(typeid(T1));

      if(it1 != types.end())
      {
         const ProviderCollection& providers = it1->second;

         // Find default provider.
         ProviderCollection::const_iterator it2 = providers.find("");

         if(it2 != providers.end())
            return boost::any_cast<boost::function<T1 ()> >(it2->second)();

         // Find first provider.
         ProviderCollection::const_iterator it4 = providers.begin();

         if(it4 != providers.end())
            return boost::any_cast<boost::function<T1 ()> >(it4->second)();

         throw ProviderNotFoundException("");
      }

      throw TypeNotFoundException("");
   }


   template<class T1>
   T1 ResolverCache::Resolve( const std::string& name ) const 
   {
      // Find type to inject.
      TypeCollection::const_iterator it1 = types.find(typeid(T1));

      if(it1 != types.end())
      {
         const ProviderCollection& providers = it1->second;

         // Find named provider.
         ProviderCollection::const_iterator it2 = providers.find(name);

         if(it2 != providers.end())
            return boost::any_cast<boost::function<T1 ()> >(it2->second)();

         // Find default provider.
         ProviderCollection::const_iterator it3 = providers.find("");

         if(it3 != providers.end())
            return boost::any_cast<boost::function<T1 ()> >(it3->second)();

         // Find first provider.
         ProviderCollection::const_iterator it4 = providers.begin();

         if(it4 != providers.end())
            return boost::any_cast<boost::function<T1 ()> >(it4->second)();

         throw ProviderNotFoundException("");
      }

      throw TypeNotFoundException("");
   }
}
}

#endif   // IOCPP_RESOLVERCACHE_H
